/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is part of dcm4che, an implementation of DICOM(TM) in
* Java(TM), hosted at https://github.com/gunterze/dcm4che.
*
* The Initial Developer of the Original Code is
* Agfa Healthcare.
* Portions created by the Initial Developer are Copyright (C) 2012-2014
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* See @authors listed below
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.dcm4chee.storage.cloud;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.inject.Named;
import org.dcm4che3.net.Device;
import org.dcm4chee.storage.ObjectAlreadyExistsException;
import org.dcm4chee.storage.ObjectNotFoundException;
import org.dcm4chee.storage.RetrieveContext;
import org.dcm4chee.storage.StorageContext;
import org.dcm4chee.storage.conf.StorageDevice;
import org.dcm4chee.storage.conf.StorageSystem;
import org.dcm4chee.storage.spi.StorageSystemProvider;
import org.jclouds.ContextBuilder;
import org.jclouds.apis.ApiMetadata;
import org.jclouds.apis.Apis;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.io.Payload;
import org.jclouds.io.payloads.InputStreamPayload;
import org.jclouds.providers.ProviderMetadata;
import org.jclouds.providers.Providers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.io.CountingInputStream;
import static org.jclouds.Constants.*;
import static org.jclouds.filesystem.reference.FilesystemConstants.*;
/**
* @author Steve Kroetsch<stevekroetsch@hotmail.com>
*
*/
@Named("org.dcm4chee.storage.cloud")
@Dependent
public class CloudStorageSystemProvider implements StorageSystemProvider {
private static Logger log = LoggerFactory
.getLogger(CloudStorageSystemProvider.class);
private StorageSystem system;
private BlobStoreContext context;
private MultipartUploader multipartUploader;
@Inject @StorageDevice
private Device device;
@Override
public void init(StorageSystem storageSystem) {
String api = storageSystem.getStorageSystemAPI();
if (api == null) {
throw new IllegalArgumentException(
String.format(
"%s missing required API from list of jclouds supported APIs and providers: %s",
storageSystem, getApisAndProviders()));
}
this.system = storageSystem;
ContextBuilder ctxBuilder = ContextBuilder.newBuilder(api);
String identity = storageSystem.getStorageSystemIdentity();
if (identity != null)
ctxBuilder.credentials(identity,
storageSystem.getStorageSystemCredential());
String path = storageSystem.getStorageSystemPath();
Properties overrides = new Properties();
if (path != null) {
if ("filesystem".equals(api))
overrides.setProperty(PROPERTY_BASEDIR, Paths.get(path)
.toAbsolutePath().toString());
else
ctxBuilder.endpoint(path);
}
overrides.setProperty(PROPERTY_MAX_CONNECTIONS_PER_CONTEXT,
String.valueOf(storageSystem.getMaxConnections()));
overrides.setProperty(PROPERTY_CONNECTION_TIMEOUT,
String.valueOf(storageSystem.getConnectionTimeout()));
overrides.setProperty(PROPERTY_SO_TIMEOUT,
String.valueOf(storageSystem.getSocketTimeout()));
ctxBuilder.overrides(overrides);
context = ctxBuilder.buildView(BlobStoreContext.class);
long partSize = system.getMultipartUploadSizeInBytes();
multipartUploader = partSize > 0 ? MultipartUploader
.newMultipartUploader(context, partSize) : null;
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
context.close();
}
});
}
private static List<String> getApisAndProviders() {
List<String> l = new ArrayList<String>();
for (ProviderMetadata md : Providers.all())
l.add(md.getId());
for (ApiMetadata md : Apis.all())
l.add(md.getId());
return l;
}
@Override
public void checkWriteable() {
}
@Override
public long getUsableSpace() {
return Long.MAX_VALUE;
}
@Override
public long getTotalSpace() throws IOException {
return Long.MAX_VALUE;
}
@Override
public OutputStream openOutputStream(final StorageContext ctx,
final String name) throws IOException {
final PipedInputStream in = new PipedInputStream();
final FutureTask<Void> f = new FutureTask<Void>(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
upload(ctx, in, name);
} finally {
in.close();
}
return null;
}
});
OutputStream out = new PipedOutputStream(in) {
@Override
public void close() throws IOException {
super.close();
try {
f.get();
} catch (InterruptedException e) {
throw new InterruptedIOException();
} catch (ExecutionException e) {
Throwable c = e.getCause();
if (c instanceof IOException)
throw (IOException) c;
throw new IOException("Upload failed", c);
}
}
};
device.execute(f);
return out;
}
@Override
public void copyInputStream(StorageContext ctx, InputStream in, String name)
throws IOException {
upload(ctx, in, name);
}
private void upload(StorageContext ctx, InputStream in, String name)
throws IOException {
upload(ctx, in, name, -1L);
}
private void upload(StorageContext ctx, InputStream in, String name,
long len) throws IOException {
String container = system.getStorageSystemContainer();
BlobStore blobStore = context.getBlobStore();
if (blobStore.blobExists(container, name))
throw new ObjectAlreadyExistsException(
system.getStorageSystemPath(), container + '/' + name);
CountingInputStream cin = new CountingInputStream(in);
Payload payload = new InputStreamPayload(cin);
if (len != -1) {
payload.getContentMetadata().setContentLength(len);
}
Blob blob = blobStore.blobBuilder(name).payload(payload).build();
String etag = (multipartUploader != null) ? multipartUploader.upload(
container, blob) : blobStore.putBlob(container, blob);
ctx.setFileSize(cin.getCount());
log.info("Uploaded[uri={}, container={}, name={}, etag={}]",
system.getStorageSystemPath(), container, name, etag);
}
@Override
public void storeFile(StorageContext ctx, Path source, String name)
throws IOException {
try (InputStream in = Files.newInputStream(source,
StandardOpenOption.READ)) {
upload(ctx, in, name, Files.size(source));
}
}
@Override
public void moveFile(StorageContext ctx, Path source, String name)
throws IOException {
storeFile(ctx, source, name);
Files.delete(source);
}
@Override
public InputStream openInputStream(RetrieveContext ctx, String name)
throws IOException {
BlobStore blobStore = context.getBlobStore();
String container = system.getStorageSystemContainer();
Blob blob = blobStore.getBlob(container, name);
if (blob == null)
throw new ObjectNotFoundException(system.getStorageSystemPath(),
container + '/' + name);
return blob.getPayload().openStream();
}
@Override
public Path getFile(RetrieveContext ctx, String name) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void deleteObject(StorageContext ctx, String name)
throws IOException {
BlobStore blobStore = context.getBlobStore();
String container = system.getStorageSystemContainer();
if (!blobStore.blobExists(container, name))
throw new ObjectNotFoundException(system.getStorageSystemPath(),
container + '/' + name);
blobStore.removeBlob(container, name);
}
@Override
public Path getBaseDirectory(StorageSystem system) {
throw new UnsupportedOperationException();
}
@Override
public <E extends Enum<E>> E queryStatus(RetrieveContext ctx, String name,
Class<E> enumType) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void sync (List<String> names) throws IOException {
//do nothing.
}
}